package com.leetcode.basic.synchronize.ReentrantLock;


import com.leetcode.utils.ThreadUtils;

import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import static com.leetcode.utils.ThreadUtils.sleep;

/**
 * https://coolxing.iteye.com/blog/1236909
 * <p>
 * - 读取锁允许多个reader线程同时持有, 而写入锁最多只能有一个writter线程持有.<br>
 * - 读写锁的使用场合: 读取共享数据的频率远大于修改共享数据的频率. 在上述场合下, 使用读写锁控制共享资源的访问, 可以提高并发性能.<br>
 * - 如果一个线程已经持有了写入锁, 则可以再持有读写锁（质疑）. 相反, 如果一个线程已经持有了读取锁, 则在释放该读取锁之前, 不能再持有写入锁.<br>
 * - 可以调用写入锁的newCondition()方法获取与该写入锁绑定的Condition对象, 此时与普通的互斥锁并没有什么区别. 但是调用读取锁的newCondition()方法将抛出异常.<br>
 */
public class ReadWriteLockDemo {

	ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
	Lock          writeLock     = readWriteLock.writeLock();
	Lock          readLock      = readWriteLock.readLock();

	/**
	 * 读取锁允许多个reader线程同时持有
	 */
	@Test
	public void test0() {
		Run run0;
		Run run1;
		Run run2;
		new Thread(run0 = new Run(readLock)).start();
		new Thread(run1 = new Run(readLock)).start();
		new Thread(run2 = new Run(readLock)).start();

		sleep(2000);

		// readLock可以让多个线程获取
		Assert.assertTrue(run0.tryLock);
		Assert.assertTrue(run1.tryLock);
		Assert.assertTrue(run2.tryLock);
	}

	/**
	 * 写入锁最多只能有一个writter线程持有
	 */
	@Test
	public void test1() {
		Run run0;
		Run run1;
		new Thread(run0 = new Run(writeLock)).start();
		new Thread(run1 = new Run(writeLock)).start();

		sleep(2000);

		// writeLock只能让一条线程获取
		Assert.assertTrue(run0.tryLock);
		Assert.assertFalse(run1.tryLock);
	}

	@Test
	public void test2() {
		Run run0;
		Run run1;
		new Thread(run0 = new Run(writeLock)).start();
		new Thread(run1 = new Run(readLock)).start();

		// 获取writeLock后，获取不到ReadLock

		sleep(2000);

		Assert.assertTrue(run0.tryLock);
		Assert.assertFalse(run1.tryLock);
	}

	/**
	 * 如果一个线程已经持有了读取锁, 则在释放该读取锁之前, 不能再持有写入锁。
	 */
	@Test
	public void test3() {
		Run run0;
		Run run1;
		new Thread(run0 = new Run(readLock)).start();// 先获取读取锁
		new Thread(run1 = new Run(writeLock)).start();

		sleep(2000);

		Assert.assertTrue(run0.tryLock);
		Assert.assertFalse(run1.tryLock);
	}

	class Run implements Runnable {
		Lock    lock;
		boolean tryLock;

		public Run(Lock lock) {
			sleep(1);// 保证上一条线程先创建
			this.lock = lock;
		}

		@Override
		public void run() {
			tryLock = lock.tryLock();

			System.out.println(getName(lock) + " tryLock=" + tryLock);

//			if (!tryLock) {
//				// 获取不到锁
//				System.err.println("获取不到锁 " + getName(lock));
//				return;
//			}
			ThreadUtils.sleep(1000);

			lock.unlock();

			System.out.println(getName(lock) + " unlock");
		}

		private String getName(Lock lock) {
			String str = lock.toString().replace("java.util.concurrent.locks.ReentrantReadWriteLock$", "");
			int    n0  = str.indexOf('@');
			int    n1  = str.indexOf('[');
			return str.substring(0, n0) + str.substring(n1);
		}
	}
}
